#ifndef RENGINE_H
#define RENGINE_H

#include <QObject>
#include <QFile>
#include <QList>
#include <QTextStream>
#include <QHash>
#include <QSqlQuery>
#include <QMap>
#include <QVariant>
#include <QFutureWatcher>
#include <QTextCodec>
#include <QTimer>
#include "rfileinfo.h"
#include "rbookmark.h"

class RRecentFileModel;

class RFileModel;

class REngine : public QObject
{
    Q_OBJECT
    Q_ENUMS(Settings)
    Q_ENUMS(OpenFileResult)
public:
    enum Settings {
        TextColor,
        TextSmooth,
        TextLineSpace,
        TextBackgroud,
        TextFontBold,
        TextFontFamiliy,
        TextFontItalic,
        TextFontLetterSpacing,
        TextFontPixelSize,
        TextFontPointSize,
        TextFontStrikeout,
        TextFontUnderline,
        TextFontWeight,
        TextFontWordSpacing,
        TextIgnoreEmptyLine,
        Codec,
        Orientation,
        ShowToolbar,
        LastReadPath,
        ShowQuickSetting,
        AutoPageTurning,
        AutoPageTurningTimeOut,
        ShowAutoPageTurningTime,
        DisableDisplayBlank,
        NightMode
    };

    enum OpenFileResult {
        OpenFileNone,
        OpenFileSuccess,
        OpenFileFailed,
        OpenFileSuccessWithDefaultCodec
    };

    explicit REngine(QObject *parent = 0);
    ~REngine();
    Q_INVOKABLE void asyncOpenFile(QString fileName);
    Q_INVOKABLE int fileLineNumber(QString fileName);
    Q_INVOKABLE QString getLine(QString fileName, int lineNumber);
    RFileModel* fileModel(QString fileName);

    //Recent file
    Q_INVOKABLE QObject* recentFileModel();
    Q_INVOKABLE bool resetModel(QObject* model);
    Q_INVOKABLE bool removeRecentFileInfo(QString fileName, bool notify = true);
    Q_INVOKABLE void removeAllRecentFileInfos();

    //Last position
    Q_INVOKABLE void saveLastPosition(QString fileName, int lineNumber, int charIndex,  bool saveLastReadTime = true);
    Q_INVOKABLE int lastLineNumber(QString fileName);
    Q_INVOKABLE int lastCharIndex(QString fileName);

    //Setting
    Q_INVOKABLE QVariant setting(Settings id);
    Q_INVOKABLE void setSetting(Settings id, QVariant value);
    Q_INVOKABLE int fileSize(QString fileName);

    Q_INVOKABLE int lineNumberOfPercent(QString fileName, float percent);
    Q_INVOKABLE float lineNumberToPercent(QString fileName, int lineNum);
    Q_INVOKABLE QString epocToString(qint64 epocTime);


    //File codec
    Q_INVOKABLE QVariant availableCodecs(bool addAuto = true);
    Q_INVOKABLE QVariant availableCodecRegions();
    Q_INVOKABLE QVariant availableCodecsForRegion(QString region);
    Q_INVOKABLE QString fileCodec(QString fileName);
    Q_INVOKABLE QString fileCurrentCodec(QString fileName);
    Q_INVOKABLE void saveFileCodec(QString fileName, QString codec);

    //
    Q_INVOKABLE bool isBlankString(QString string);
    Q_INVOKABLE bool pathExist(QString path);
    Q_INVOKABLE void copyToClipboard(QString text);

    //Bookmark
    Q_INVOKABLE int saveBookmark(QString fileName, int lineNum,
                                 int charIndex, QString note);
    Q_INVOKABLE QList<QObject*> bookmarks(QString fileName);
    Q_INVOKABLE bool removeBookmark(int bookmarkId);
    Q_INVOKABLE bool removeAllBookmarks(QString fileName);

    //Chapter
    Q_INVOKABLE void asyncGetChapters(QString fileName);
    Q_INVOKABLE void saveLastPostionToChapter(QString fileName,
                                              int charpterIndex);
    Q_INVOKABLE bool showChapterMenu();
    Q_INVOKABLE int currentChapterIndex(QString fileName);

    //Temporarily set disable display blank; for permanent, use setSetting()
    Q_INVOKABLE bool setDisableDisplayBlank(bool enable);

    //Font families
    Q_INVOKABLE QVariant fontFamilies();

    bool fileLinePosition(const QString& fileName,
                          QHash<int, qint64>& linePosition,
                          QList<int>& chapterLines);

    void saveFileLinePosition(const QString& fileName,
                              const QHash<int, qint64>& linePosition,
                              const QList<int>& chapterLine);
    bool fileExistInDb(const QString& fileName);
    int fileId(const QString& fileName);
    bool recentFileInfo(QList<RFileInfo>& infoList);
    static int openFile(REngine* engine, QString fileName);
    QTextCodec *codecForString(QByteArray &stringArray, bool& useDefault);
    void chapterKeys(const QString& codec, QList<QString>& keys);
    static QVariant chapters(REngine* engine, QString fileName);

//Utilities
    qint32 byteArrayToInt(QByteArray array, bool littleEndian = true);
    quint32 byteArrayToUint(QByteArray array, bool littleEndian = true);
    qint16 byteArrayToInt16(QByteArray array, bool littleEndian = true);
    quint16 byteArrayToUint16(QByteArray array, bool littleEndian = true);
    char byteArrayToChar(QByteArray array);
    uchar byteArrayToUchar(QByteArray array);
    QString storageLocation();
    QString uncompressedContentFileName(const QString& fileName);

signals:
    void recentFileInfoChanged();
    void fileOpened(QString fileName, int result);
    void getChapterResult(QVariant chapters);
    void bookmarkChanged();
    void timeout();

public slots:
    void openFileFinished();
    void getChapterFinished();
    void displayBlankFeedTimeOut();

private:
    /**
     * Query data specified by sqlStr
     * @param sqlStr SELECT or INSERT like SQL string
     * @return QSqlQuery* if find data or NULL
     */
    QSqlQuery *query(QString &sqlStr, bool select = true);
    int addFileToDb(const QString& fileName, int lineCount);
    bool readLinePosition(const QString& linePositionFileName,
                          QHash<int, qint64>& linePositions,
                          QList<int>& chapterLines);
    bool initStorageLocation();
    QString linePositionFileName(const QString& fileName);
    QString settingFileName();
    QString DatabaseName();
    bool initDb();
    bool commitDb();
    bool createFileTable();
    bool createBookmarkTable();
    bool createTable(QString &sql);
    void showAllInFileTable();
    void saveSettings();
    void saveSetting(const QString &setting, const QVariant& value);
    void loadSettings();
    void decodeFileName(QString fileName);
    QVariant adjustValueType(REngine::Settings id, QVariant value);
    REngine::Settings settingStrToId(const QString & settingStr);
    void reloadAllFileModels();
    void reloadFileModelForCodec();
    bool removeFileFromDB(QString fileName);
    bool removeRealFile(QString fileName) ;
    void removeFileModel(QString fileName);
    QString systemLanguageCode();
    QString defaultCodec();
    QString enca2QtCodec(const QString &encaCodec);

private:
    class InsertUpdateHelper {
    public:
        InsertUpdateHelper(const QString& tableName);
        void setTable(const QString& tableName);
        void setId(const QString& idColumn, int id);
        bool insertSql(QString& sqlString);
        bool updateSql(QString& sqlString);
        void addNull(const QString& column);
        void addValue(const QString& column, const QString& value);
        void addValue(const QString& column, const int& value);
        void addValue(const QString& column, const qint64 &value);
        void addValue(const QString& column, const double& value);
        void addValue(const QString& column, const bool& value);
    private:
        QString m_table_name;
        QMap<QString, QString> m_column_value;
        QString m_where;
    };


private:
    QMap<QString, RFileModel*> m_fileName_model_map;
    //Key, value
    QMap<QString, QVariant> m_settings;
    //Id, Key, Default value
    static const QVariant settingsMap[][4];

    //Language id, language code, default codec
    static const QVariant languageCodecMap[][3];

    //enca codec, Qt codec
    static const QVariant enca2QtCodecMap[][2];
    QHash<QFutureWatcher<int>*, QString> m_open_file_watchers;
    QFutureWatcher<int> m_open_file_watcher;
    QFutureWatcher<QVariant> m_get_chapter_watcher;
    QList<void*> m_runners;
    QTimer m_display_blank_feed_timer;
};

#endif // RENGINE_H
